﻿#region Legal

// Jimmy Zimmerman
// Team Mongoose
//
// END USER LICENSE AGREEMENT
// IMPORTANT - READ THIS CAREFULLY:  This End User License Agreement is a legal agreement
// between you (either an individual, business entity, currently active identity of an
// individual with Multiple Personality Disorder, octopus overlord, or otherworldly entity),
// and Team Mongoose, for the enclosed, components.
//
// By reading this document and/or installing this product, you agree to be bound by the
// terms of this EULA.
//
// Team Mongoose owns all copyright, trade secret, trademark, trade wars,
// patent, portent, and potential rights to these components.  Team Mongoose
// grants you the right to deploy the enclosed components.
//
// If you agree to the terms of this EULA, a license to use these components is granted to you.
//
// If you should happen to benefit from the enclosed components, then you are legally and
// contractually bound to thank us for it. Send your regards to jimmyzimms@hotmail.com.
//
// OTHER RIGHTS AND LIMITATONS
// You may not reverse-engineer, decompile, decode, disassemble, psycho-analyze, or otherwise
// attempt to find hidden meanings between the lines of this EULA; unless, of course, you should
// happen to find some, and they are funny.
//
// You may not translate this EULA or any part of the components into Ancient Sumerian.
//
// THESE COMPONENTS ARE PROVIDED “AS-IS” WITHOUT WARRANTY OF ANY KIND. ANY USE OF THE COMPONENTS
// CONTAINED IS AT YOUR OWN RISK. TEAM MONGOOSE DISCLAIM ALL WARRANTIES, EITHER
// EXPRESS OR IMPLIED, WITH RESPECT TO THE ACCURRACY AND CORRECTNESS OF THE COMPONENTS CONTAINED
// HEREIN. TEAM MONGOOSE DOES NOT WARRANT THAT THE COMPONENTS ARE FLAWLESS.
//
// REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS, WITH OR WITHOUT MODIFICATION, ARE PERMITTED
// PROVIDED THAT THE FOLLOWING CONDITIONS ARE MET:
// * REDISTRIBUTIONS OF SOURCE CODE MUST RETAIN THE ABOVE COPYRIGHT NOTICE
// * REDISTRIBUTIONS IN BINARY FORM MUST NOTE THE USE OF THE COMPONENT IN DOCUMENTATION AND/OR
//   OTHER MATERIALS PROVIDED WITH THE DISTRIBUTION.
// * NEITHER THE NAME OF TEAM MONGOOSE MAY BE USED TO ENDORES OR PROMOTE PRODUCTS
//   DERIVED FROM THIS SOFTWARE WITHOUT SPECIFIC PRIOR WRITTEN PERMISSION.
//
// IN NO EVENT SHALL TEAM MONGOOSE BE HELD LIABLE FOR INCIDENTAL, SPECIAL, INDIRECT,
// INCONSEQUENTIAL, UNBELIEVABLE, EXAGGERATED, VERBOSE, OR TYPICAL DAMAGES INCURRED WHILE USING
// THE ENCLOSED COMPONENTS.
//
// OUR STUFF ALWAYS WORKS - SOMETIMES.

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Mongoose.Games.BfgRoster
{
    /// <summary>
    /// Provides extension methods for the <see cref="IEnumerable{T}"/> type that are conspicuously absent from the .Net framework.
    /// </summary>
    /// <threadsafety static="true" instance="true" />
    public static class EnumerableExtensions
    {
        /// <summary>
        /// Realizes the current chain of evaluation (including lazy evaluated items) and returns the original <paramref name="sequence"/>.
        /// </summary>
        /// <remarks><note type="warning">Make sure that the supplied <paramref name="sequence"/> is safe to be iterated multiple times.</note></remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to realize the current evaluation chain on.</param>
        /// <returns>The original sequence.</returns>
        [SuppressMessage("Microsoft.StyleCop.CSharp.SpacingRules", "SA1002:SemicolonsMustBeSpacedCorrectly", Justification = "This is an empty statement.")]
        [SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1106:CodeMustNotContainEmptyStatements", Justification = "By design.  This is an element realization.")]
        public static IEnumerable<T> Apply<T>(this IEnumerable<T> sequence)
        {
            if (sequence == null)
                return null;

            using (var e = sequence.GetEnumerator())
            {
                // no op
                while (e.MoveNext());
            }

            return sequence;
        }

        /// <summary>
        /// Performs the specified action on each element of the <paramref name="sequence"/>.
        /// </summary>
        /// <remarks>
        /// <para>The <see cref="Action{T}"/> is a delegate to a method that performs an action on the object passed to it. The elements of the current <see cref="IEnumerable{T}"/> are individually passed to the <see cref="IEnumerable{T}"/> delegate.</para>
        /// <para>This method is an O(n) operation, where n is count.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="action"/> over.</param>
        /// <param name="action">The <see cref="Action{T}"/> delegate to perform on each element of the <see cref="IEnumerable{T}"/>.</param>
        public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
        {
            sequence.ForEach(action, item => true);
        }

        /// <summary>
        /// Performs the specified action on each element of the <paramref name="sequence"/> that matches the supplied <paramref name="predicate"/>.
        /// </summary>
        /// <remarks>
        /// <para>The <see cref="Action{T}"/> is a delegate to a method that performs an action on the object passed to it. The elements of the current <see cref="IEnumerable{T}"/> are individually passed to the <see cref="IEnumerable{T}"/> delegate.</para>
        /// <para>This method is an O(n) operation, where n is count.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="action"/> over.</param>
        /// <param name="action">The <see cref="Action{T}"/> delegate to perform on each element of the <see cref="IEnumerable{T}"/>.</param>
        /// <param name="predicate">The <see cref="Func{T,TResult}">function</see> to test each element for a condition.</param>
        public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action, Func<T, Boolean> predicate)
        {
            if (sequence != null && action != null && predicate != null)
                sequence.Where(predicate).ToList().ForEach(action);
        }

        /// <summary>Provides a fluent interface to the <see cref="ForEach{T}(IEnumerable{T},System.Action{T})">ForEach</see> method.</summary>
        /// <remarks>
        /// The supplied <paramref name="action"/> will be executed for each item while iterating the result. The entire <paramref name="sequence"/> is only
        /// evaluated if the iterator is completly executed across all members. Any exception thrown by <paramref name="action"/> will usually cease all
        /// evaluatation of the iterator, therefore, the supplied <see cref="Action{T}"/> should be side effect free on the original source sequence.
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="action"/> over.</param>
        /// <param name="action">The <see cref="Action{T}"/> delegate to perform on each element of the <see cref="IEnumerable{T}"/>.</param>
        /// <returns>An <see cref="IEnumerable{T}">enumeration</see> with the same items as the source after evaluation.</returns>
        public static IEnumerable<T> With<T>(this IEnumerable<T> sequence, Action<T> action)
        {
            return sequence.With(action, item => true);
        }

        /// <summary>Provides a fluent interface to the <see cref="ForEach{T}(IEnumerable{T},System.Action{T},System.Func{T,bool})">ForEach</see> method.</summary>
        /// <remarks>
        /// The supplied <paramref name="action"/> will be executed for each item matching <paramref name="predicate"/> while iterating the result.
        /// The entire <paramref name="sequence"/> is only evaluated if the iterator is completly executed across all members. Any exception thrown
        /// by <paramref name="action"/> will usually cease all evaluatation of the iterator, therefore, the supplied <see cref="Action{T}"/> should 
        /// be side effect free on the original source sequence.
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="action"/> over.</param>
        /// <param name="action">The <see cref="Action{T}"/> delegate to perform on each element of the <see cref="IEnumerable{T}"/>.</param>
        /// <param name="predicate">The <see cref="Func{T,TResult}">function</see> to test each element for a condition.</param>
        /// <returns>An <see cref="IEnumerable{T}">enumeration</see> with the items after evaluation from the source that were matched by the supplied predicate.</returns>
        public static IEnumerable<T> With<T>(this IEnumerable<T> sequence, Action<T> action, Func<T, Boolean> predicate)
        {
            sequence = sequence.Where(predicate);

            foreach (var item in sequence)
            {
                action(item);
                yield return item;
            }
        }

        /// <summary>
        /// Searches for the specified <paramref name="item"/> and returns the index of the first occurrence within the <paramref name="sequence"/>.
        /// </summary>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to search.</param>
        /// <param name="item">The object to locate in <paramref name="sequence"/>.</param>
        /// <param name="comparer">The <see cref="IEqualityComparer{TItem}"/> used to compare items for equality.</param>
        /// <returns>The zero-based index the specified item or -1 if no match is found.</returns>
        public static Int32 IndexOf<T>(this IEnumerable<T> sequence, T item, IEqualityComparer<T> comparer)
        {
            if (sequence == null)
                return -1;

            comparer = comparer ?? EqualityComparer<T>.Default;
            var index = 0;

            using (var enumerator = sequence.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    var current = enumerator.Current;

                    if (comparer.Equals(current, item))
                        return index;

                    index++;
                }
            }

            return -1;
        }

        /// <summary>
        /// Bypasses a specified number of elements in a sequence and then returns the remaining elements up to the limit specified in <paramref name="pageSize"/>.
        /// </summary>
        /// <remarks>
        /// <para>This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its <seealso cref="IEnumerable{T}.GetEnumerator"/> method directly or by using <see langkeyword="foreach">foreach</see>.</para>
        /// <para>This method enumerates source and yields elements until <paramref name="pageSize"/> elements have been yielded or source contains no more elements.</para>
        /// <para>If <paramref name="sequence"/> contains fewer than <seealso cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})">count</seealso> elements, an empty <see cref="IEnumerable{T}"/> is returned. If <seealso cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})">count</seealso> is less than or equal to zero, all elements of source are yielded.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">An <see cref="IEnumerable{T}"/> to return elements from.</param>
        /// <param name="pageNumber">The number pages of elements to skip before returning the remaining elements. The index is 0 based.</param>
        /// <param name="pageSize">The number of elements to return in a single page. The index is 1 based.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <para><paramref name="pageNumber"/> is less than 0</para>
        /// <para>-OR-</para>
        /// <para><paramref name="pageSize"/> is less than 1</para>
        /// </exception>
        /// <returns>An <see cref="IEnumerable{T}"/> that contains the specified number of elements from the start of the input sequence from <paramref name="pageNumber"/>.</returns>
        public static IEnumerable<T> Between<T>(this IEnumerable<T> sequence, Int32 pageNumber, Int32 pageSize)
        {
            if (sequence == null)
                return new T[0];

            if (pageNumber < 0)
                throw new ArgumentOutOfRangeException("pageNumber", "Value must be greater than zero");
            if (pageSize < 1)
                throw new ArgumentOutOfRangeException("pageSize", "Value must be greater than zero");

            var startIndex = pageNumber * pageSize;
            return sequence.Skip(startIndex).Take(pageSize);
        }

        /// <summary>
        /// Calculates the number of subsets for a sequence based on a particular page size .
        /// </summary>
        /// <remarks>
        /// <para>This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its <seealso cref="IEnumerable{T}.GetEnumerator"/> method directly or by using <see langkeyword="foreach">foreach</see>.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">An <see cref="IEnumerable{T}"/> to return elements from.</param>
        /// <param name="pageSize">The number of elements to evaludate in a single page. The index is 1 based.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="pageSize"/> is less than 1</exception>
        /// <returns>A count that contains the calculated number of subsets that are contained in <paramref name="sequence"/>.</returns>
        public static Int32 PageCount<T>(this IEnumerable<T> sequence, Int32 pageSize)
        {
            if (sequence == null)
                return 0;

            if (pageSize < 1)
                throw new ArgumentOutOfRangeException("pageSize", "Value must be greater than zero");

            var count = sequence.Count();
            var pageCount = count / pageSize;
            pageCount = count % pageSize != 0 ? pageCount + 1 : pageCount;

            return pageCount;
        }

        /// <summary>
        /// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the entire <see cref="IEnumerable{T}"/>.
        /// </summary>
        /// <remarks>
        /// <para>The <see cref="IEnumerable{T}"/> is searched forward starting at the first element and ending at the last element.</para>
        /// <para>The <see cref="Predicate{T}"/> is a delegate to a method that returns <see langkeyword="true">true</see> if the object passed to it matches the conditions defined in the delegate. The elements of the current <see cref="IEnumerable{T}"/> are individually passed to the <seealso cref="Predicate{T}"/> delegate.</para>
        /// <para>This method performs a linear search; therefore, this method is an O(n) operation, where n is <see cref="Enumerable.Count{TSource}(IEnumerable{TSource})">Count</see>.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="predicate"/> over.</param>
        /// <param name="predicate">The <see cref="Predicate{T}"/> delegate that defines the conditions of the element to search for.</param>
        /// <returns>The zero-based index of the first occurrence of an element that matches the conditions defined by <paramref name="predicate"/>, if found; otherwise, –1.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is <see langkeyword="null">null</see>.</exception>
        public static Int32 FindIndex<T>(this IEnumerable<T> sequence, Func<T, Boolean> predicate)
        {
            if (sequence == null)
                return -1;

            return FindIndex(sequence, 0, predicate);
        }

        /// <summary>
        /// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within range of elements in the sequenced <see cref="IEnumerable{T}"/> that extends from the specified index to the last element.
        /// </summary>
        /// <remarks>
        /// <para>The <see cref="IEnumerable{T}"/> is searched forward starting at the first element and ending at the last element.</para>
        /// <para>The <see cref="Predicate{T}"/> is a delegate to a method that returns <see langkeyword="true">true</see> if the object passed to it matches the conditions defined in the delegate. The elements of the current <see cref="IEnumerable{T}"/> are individually passed to the <seealso cref="Predicate{T}"/> delegate.</para>
        /// <para>This method performs a linear search; therefore, this method is an O(n) operation, where n is <see cref="Enumerable.Count{TSource}(IEnumerable{TSource})">Count</see> - <paramref name="startIndex"/>.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="predicate"/> over.</param>
        /// <param name="startIndex">The zero-based starting index of the search.</param>
        /// <param name="predicate">The <see cref="Predicate{T}"/> delegate that defines the conditions of the element to search for.</param>
        /// <returns>The zero-based index of the first occurrence of an element that matches the conditions defined by <paramref name="predicate"/>, if found; otherwise, –1.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is <see langkeyword="null">null</see>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> is outside the range of valid indexes for the sequenced <see cref="IEnumerable{T}"/>.</exception>
        public static Int32 FindIndex<T>(this IEnumerable<T> sequence, Int32 startIndex, Func<T, Boolean> predicate)
        {
            if (sequence == null)
                return -1;

            return FindIndex(sequence, startIndex, sequence.Count() - startIndex, predicate);
        }

        /// <summary>
        /// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the range of elements in the sequenced <see cref="IEnumerable{T}"/> that starts at the specified index and contains the specified number of elements.
        /// </summary>
        /// <remarks>
        /// <para>The <see cref="IEnumerable{T}"/> is searched forward starting at the <paramref name="startIndex"/> and ending at <paramref name="count"/>.</para>
        /// <para>The <see cref="Predicate{T}"/> is a delegate to a method that returns <see langkeyword="true">true</see> if the object passed to it matches the conditions defined in the delegate. The elements of the current <see cref="IEnumerable{T}"/> are individually passed to the <seealso cref="Predicate{T}"/> delegate.</para>
        /// <para>This method performs a linear search; therefore, this method is an O(n) operation, where n is <see cref="Enumerable.Count{TSource}(IEnumerable{TSource})">Count</see> - <paramref name="startIndex"/> or <paramref name="count"/>, whichever is less.</para>
        /// </remarks>
        /// <typeparam name="T">The type of each element in the sequence.</typeparam>
        /// <param name="sequence">The sequence to project the supplied <paramref name="predicate"/> over.</param>
        /// <param name="startIndex">The zero-based starting index of the search.</param>
        /// <param name="count">The number of elements in the section to search.</param>
        /// <param name="predicate">The <see cref="Predicate{T}"/> delegate that defines the conditions of the element to search for.</param>
        /// <returns>The zero-based index of the first occurrence of an element that matches the conditions defined by <paramref name="predicate"/>, if found; otherwise, –1.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is <see langkeyword="null">null</see>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <para><paramref name="startIndex"/> is outside the range of valid indexes for the sequenced <see cref="IEnumerable{T}"/></para>
        /// <para>-OR-</para>
        /// <para><paramref name="count"/> is less than 0</para>
        /// <para>-OR-</para>
        /// <para><paramref name="startIndex"/> and <paramref name="count"/> do not specify a valid section in the sequenced <see cref="IEnumerable{T}"/></para>
        /// </exception>
        public static Int32 FindIndex<T>(this IEnumerable<T> sequence, Int32 startIndex, Int32 count, Func<T, Boolean> predicate)
        {
            if (sequence == null)
                return -1;

            if (startIndex > sequence.Count())
                throw new ArgumentOutOfRangeException("startIndex", string.Empty);

            if (count < 0 || startIndex > (sequence.Count() - count))
                throw new ArgumentOutOfRangeException("count", string.Empty);

            if (predicate == null)
                throw new ArgumentNullException("predicate");

            Int32 num = startIndex + count;

            for (Int32 i = startIndex; i < num; i++)
                if (predicate(sequence.ElementAt(i)))
                    return i;

            return -1;
        }
    }
}
